﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ClearingRestartingAutomaton.Common;

namespace ClearingRestartingAutomaton.Base
{
    /// <summary>
    /// Immutable structure representing the instruction.
    /// </summary>
    /// <remarks>
    /// Instruction is a triple (x, z -> t, y), where:
    /// x is the left context,
    /// y is the right context,
    /// z is the from-word, and
    /// t is the to-word.
    /// This instruction can be applied to a word w if and only if
    /// w can be factorized as w = uxzyv. After application of this 
    /// instruction we get the word w' = uxtyv.
    /// The application of the instruction is called the reduction.
    /// We say that the word w is reduced to the word w'
    /// The inverse of the instruction (x, z -> t, y) is the instruction (x, t -> z, y).
    /// The application of the inverted instruction is called the generation.
    /// We say that the word w is generated from the word w'.
    /// </remarks>
    [Serializable]
    public struct Instruction : IComparable<Instruction>, IEquatable<Instruction>
    {
        #region Constructors

        /// <summary>
        /// Constructor of this structure.
        /// </summary>
        /// <param name="leftContext">Left context.</param>
        /// <param name="fromWord">A word which is the subject of the reduction.</param>
        /// <param name="toWord">A word which is the result of the reduction.</param>
        /// <param name="rightContext">Right context.</param>
        public Instruction(
            string leftContext,
            string fromWord,
            string toWord,
            string rightContext)
        {
            this.leftContext = leftContext;
            this.rightContext = rightContext;
            this.fromWord = fromWord;
            this.toWord = toWord;
        }

        #endregion

        #region IComparable Interface

        /// <summary>
        /// Compares this instance with a specified instruction and indicates
        /// whether this instance precedes, follows, or appears in the same position
        /// in the sort order as the specified instruction.
        /// </summary>
        /// <param name="instruction">Instruction.</param>
        /// <returns>
        /// A 32-bit signed integer that indicates whether this instance precedes, follows,
        /// or appears in the same position in the sort order as the value parameter.
        /// Value Condition Less than zero: This instance precedes the value parameter. 
        /// Zero: This instance has the same position in the sort order as the value parameter. 
        /// Greater than zero: This instance follows the value parameter.
        /// </returns>
        public int CompareTo(Instruction instruction)
        {
            int cmpFromWord = this.fromWord.LengthLexCompare(instruction.fromWord);
            if (cmpFromWord != 0) { return cmpFromWord; }
            int cmpLeftContext = this.leftContext.LengthLexCompare(instruction.leftContext);
            if (cmpLeftContext != 0) { return cmpLeftContext; }
            int cmpRightContext = this.rightContext.LengthLexCompare(instruction.rightContext);
            if (cmpRightContext != 0) { return cmpRightContext; }
            return this.toWord.LengthLexCompare(instruction.toWord);
        }

        #endregion

        #region IEquatable Interface

        /// <summary>
        /// Determines whether this instance of Instruction and a specified object,
        /// which must also be an Instruction object, have the same value.
        /// </summary>
        /// <param name="obj">An System.Object.</param>
        /// <returns>
        /// True if obj is an Instruction and its value is the same as this instance;
        /// Otherwise, false.
        /// </returns>
        public override bool Equals(object obj)
        {
            if (obj is Instruction)
                return this.Equals((Instruction)obj);
            else
                return false;
        }

        /// <summary>
        /// Determines whether this instance and another specified Instruction object have the same value.
        /// </summary>
        /// <param name="instruction">Instruction.</param>
        /// <returns>
        /// True if the value of the value parameter is the same as this instance; 
        /// Otherwise, false.
        /// </returns>
        public bool Equals(Instruction instruction)
        {
            return (this.fromWord.Equals(instruction.fromWord) &&
                this.toWord.Equals(instruction.toWord) &&
                this.leftContext.Equals(instruction.leftContext) &&
                this.rightContext.Equals(instruction.rightContext));
        }

        /// <summary>
        /// Returns the hash code for this instruction.
        /// </summary>
        /// <returns>A 32-bit signed integer hash code.</returns>
        public override int GetHashCode()
        {
            return this.fromWord.GetHashCode() +
                this.toWord.GetHashCode() +
                this.leftContext.GetHashCode() +
                this.rightContext.GetHashCode();
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// Left context.
        /// </summary>
        public string LeftContext
        {
            get { return this.leftContext; }
        }

        /// <summary>
        /// Right context.
        /// </summary>
        public string RightContext
        {
            get { return this.rightContext; }
        }

        /// <summary>
        /// A word which is the subject of reduction.
        /// </summary>
        public string FromWord
        {
            get { return this.fromWord; }
        }

        /// <summary>
        /// A word which is the result of reduction.
        /// </summary>
        public string ToWord
        {
            get { return this.toWord; }
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Decides whether this instruction can be applied to the subword of the specified word
        /// starting at the given position k.
        /// </summary>
        /// <param name="word">The word to which the application of this instruction is being tested.</param>
        /// <param name="k">The starting position of the subword.</param>
        /// <param name="leftContext">Left context of the input word.</param>
        /// <param name="rightContext">Right context of the input word.</param>
        /// <returns>
        /// True if this instruction can be applied to the subword of the specified word
        /// starting at the given position k; Otherwise, false.
        /// </returns>
        public bool Match(string word, int k, string leftContext, string rightContext)
        {
            if (k + this.fromWord.Length > word.Length) return false;
            string leftPart = leftContext + word.Substring(0, k);
            string centralPart = word.Substring(k, this.fromWord.Length);
            string rightPart = word.Substring(k + this.fromWord.Length) + rightContext;
            return centralPart == this.fromWord &&
                leftPart.EndsWith(this.leftContext) &&
                rightPart.StartsWith(this.rightContext);
        }

        /// <summary>
        /// Decides whether this instruction can be applied to the subword of the specified word
        /// starting at the given position k. We suppose that the left context of the specified word 
        /// is ^ and the right contex of the specified word is $.
        /// </summary>
        /// <param name="word">The word to which the application of this instruction is being tested.</param>
        /// <param name="k">The starting position of the subword.</param>
        /// <returns>
        /// True if this instruction can be applied to the subword of the specified word
        /// starting at the given position k; Otherwise, false.
        /// </returns>
        public bool Match(string word, int k)
        {
            return Match(word, k, StringDefaultValues.LeftDelimiter, StringDefaultValues.RightDelimiter);
        }

        /// <summary>
        /// Decides whether the inverse of this instruction can be applied to the subword of the specified word
        /// starting at the given position k.
        /// </summary>
        /// <param name="word">The word to which the application of the inverse of this instruction is being tested.</param>
        /// <param name="k">The starting position of the subword.</param>
        /// <param name="leftContext">Left context of the input word.</param>
        /// <param name="rightContext">Right context of the input word.</param>
        /// <returns>
        /// True if the inverse of this instruction can be applied to the subword of the specified word
        /// starting at the given position k; Otherwise, false.
        /// </returns>
        /// <remarks>
        /// The inverse of the instruction (x, z -> t, y) is the instruction (x, t -> z, y).
        /// </remarks>
        public bool InverseMatch(string word, int k, string leftContext, string rightContext)
        {
            this.Swap();
            bool match = Match(word, k, leftContext, rightContext);
            this.Swap();
            return match;
        }

        /// <summary>
        /// Decides whether the inverse of this instruction can be applied to the subword of the specified word
        /// starting at the given position k. We suppose that the left context of the specified word 
        /// is ^ and the right contex of the specified word is $.
        /// </summary>
        /// <param name="word">The word to which the application of the inverse of this instruction is being tested.</param>
        /// <param name="k">The starting position of the subword.</param>
        /// <returns>
        /// True if the inverse of this instruction can be applied to the subword of the specified word
        /// starting at the given position k; Otherwise, false.
        /// </returns>
        /// <remarks>
        /// The inverse of the instruction (x, z -> t, y) is the instruction (x, t -> z, y).
        /// </remarks>
        public bool InverseMatch(string word, int k)
        {
            return InverseMatch(word, k, StringDefaultValues.LeftDelimiter, StringDefaultValues.RightDelimiter);
        }

        /// <summary>
        /// Applies this instruction to the subword of the specified word starting at the given position k.
        /// It throws the exception if it is not possible.
        /// </summary>
        /// <param name="word">The word to which this instruction is being applied.</param>
        /// <param name="k">The starting position of the subword.</param>
        /// <returns>
        /// The reduced word obtained from the specified word after the application of this instruction.
        /// </returns>
        public string Rewrite(string word, int k)
        {
            if (k + this.fromWord.Length > word.Length)
                throw new ArgumentOutOfRangeException();
            string leftPart = word.Substring(0, k);
            string centralPart = word.Substring(k, this.fromWord.Length);
            string rightPart = word.Substring(k + this.fromWord.Length);
            if (centralPart != this.fromWord)
                throw new ArgumentException("Instruction does not match the given word.");
            return leftPart + this.toWord + rightPart;
        }

        /// <summary>
        /// Applies the inverse of this instruction to the subword of the specified word 
        /// starting at the given position k. It throws the exception if it is not possible.
        /// </summary>
        /// <param name="word">The word to which the inverse of this instruction is being applied.</param>
        /// <param name="k">The starting position of the subword.</param>
        /// <returns>
        /// The generated word obtained from the specified word after the application of the inverse of this instruction.
        /// </returns>
        /// <remarks>
        /// The inverse of the instruction (x, z -> t, y) is the instruction (x, t -> z, y).
        /// </remarks>
        public string Generate(string word, int k)
        {
            this.Swap();
            string result = Rewrite(word, k);
            this.Swap();
            return result;
        }

        /// <summary>
        /// Returns the InstructionStepCollection structure encapsulating the enumeration of all possible 
        /// InstructionStep structures that can be applied to the given input word in the specified context.
        /// </summary>
        /// <param name="word">The word to which this instruction is applied.</param>
        /// <param name="leftContext">The left context of the given input word.</param>
        /// <param name="rightContext">The right context of the given input word.</param>
        /// <param name="use">The way the instructions are used.</param>
        /// <returns>
        /// The InstructionStepCollection structure encapsulating the the enumeration of all possible
        /// InstructionStep structures that can be applied to the given input word in the specified context.
        /// </returns>
        public InstructionStepCollection MakeStep(
            string word,
            string leftContext,
            string rightContext,
            InstructionUse use)
        {
            List<InstructionStep> steps = new List<InstructionStep>();
            for (int k = 0; k <= word.Length; ++k)
            {
                switch (use)
                {
                    case InstructionUse.Reduction:
                        if (this.Match(word, k, leftContext, rightContext))
                            steps.Add(new InstructionStep(
                                this, use, word, k,
                                this.FromWord.Length,
                                this.Rewrite(word, k)));
                        break;
                    case InstructionUse.Generation:
                        if (this.InverseMatch(word, k, leftContext, rightContext))
                            steps.Add(new InstructionStep(
                                this, use, word, k,
                                this.ToWord.Length,
                                this.Generate(word, k)));
                        break;
                    default:
                        throw new InvalidProgramException("Unknown instruction use.");
                }
            }
            return new InstructionStepCollection(word, leftContext, rightContext, use, steps);
        }

        /// <summary>
        /// Returns the InstructionStepCollection structure encapsulating the enumeration of all possible 
        /// InstructionStep structures that can be applied to the given input word in the default context (^, $).
        /// </summary>
        /// <param name="word">The word to which this instruction is applied.</param>
        /// <param name="use">The way this instruction is used.</param>
        /// <returns>
        /// The InstructionStepCollection structure encapsulating the the enumeration of all possible InstructionStep 
        /// structures that can be applied to the given input word in the default context (^, $).
        /// </returns>
        public InstructionStepCollection MakeStep(string word, InstructionUse use)
        {
            return this.MakeStep(word, StringDefaultValues.LeftDelimiter, StringDefaultValues.RightDelimiter, use);
        }

        /// <summary>
        /// Returns a System.String that represents this instruction.
        /// </summary>
        /// <returns>A System.String that represents this instruction.</returns>
        public override string ToString()
        {
            if (String.IsNullOrEmpty(this.toWord))
                return String.Format("[{0}] {1} [{2}]", this.leftContext, this.fromWord, this.rightContext);
            else
                return String.Format("[{0}] {1} -> {2} [{3}]", this.leftContext, this.fromWord, this.toWord, this.rightContext);
        }

        #endregion

        #region Private Methods

        private void Swap()
        {
            string swap = this.fromWord;
            this.fromWord = this.toWord;
            this.toWord = swap;
        }

        #endregion

        #region Private Data Members

        private string leftContext;
        private string rightContext;
        private string fromWord;
        private string toWord;

        #endregion
    }
}
